package com.xlh.dokka.svc.core.client;

import com.alibaba.fastjson.JSON;
import com.xlh.dokka.svc.core.model.Container;
import com.xlh.dokka.svc.core.model.ContainerConfig;
import com.xlh.dokka.svc.task.ImagePullListener;
import com.xlh.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author cheer
 */
@Slf4j
public class DockerClient {

    private static RestTemplate restTemplate = SpringContextUtil.getBean("dokkaRestTemplate", RestTemplate.class);

    private static RestTemplate globalRestTemplate = SpringContextUtil.getBean(RestTemplate.class);

    private static Map<String, Long> lastPingErrorMap = new ConcurrentHashMap<>();

    public static Boolean ping(String host) {
        String url = String.format("http://%s:2375/v1.23/_ping", host);
        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200) {
                if (StringUtils.equals(responseEntity.getBody(), "OK")) {
                    return true;
                }
            }
        } catch (Exception e) {
            if (lastPingErrorMap.get(host) != null && System.currentTimeMillis() - lastPingErrorMap.get(host) < 30 * 1000) {
                return false;
            }
            lastPingErrorMap.put(host, System.currentTimeMillis());
            log.warn("host {} is unhealthy, cause: {}", host, e.getMessage());
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    public static List<String> listRunningContainers(String host) {
        String url = String.format("http://%s:2375/v1.23/containers/json", host);
        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200) {
                List<Map<String, String>> containers = JSON.parseObject(responseEntity.getBody(), List.class);
                List<String> dockerIds = new ArrayList<>();
                if (containers != null) {
                    for (Map<String, String> container : containers) {
                        dockerIds.add(container.get("Id"));
                    }
                }
                return dockerIds;
            }
        } catch (Exception e) {
            log.warn("host {} list containers failed, cause: {}", host, e.getMessage());
        }
        return null;
    }

    public static Container inspectContainer(String host, String dockerId) {
        String url = String.format("http://%s:2375/v1.23/containers/%s/json", host, dockerId);
        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200) {
                ContainerInspectResponse containerInspectResponse = JSON.parseObject(responseEntity.getBody(),
                        ContainerInspectResponse.class);
                Container container = new Container();
                container.setDockerId(containerInspectResponse.getId());
                container.setName(containerInspectResponse.getName());
                container.setStatus(containerInspectResponse.getState().getStatus());
                if (containerInspectResponse.getNetworkSettings().getNetworks() != null) {
                    for (Map.Entry<String, Map<String, String>> network :
                            containerInspectResponse.getNetworkSettings().getNetworks().entrySet()) {
                        if ("bridge".equals(network.getKey())) {
                            container.setIp(network.getValue().get("IPAddress"));
                        }
                    }
                }
                if (containerInspectResponse.getNetworkSettings().getPorts() != null) {
                    for (Map.Entry<String, List<Map<String, String>>> port :
                            containerInspectResponse.getNetworkSettings().getPorts().entrySet()) {
                        if ("22/tcp".equals(port.getKey()) && port.getValue() != null) {
                            container.setSshExport(Integer.valueOf(port.getValue().get(0).get("HostPort")));
                        } else if ("5901/tcp".equals(port.getKey()) && port.getValue() != null) {
                            container.setVncExport(Integer.valueOf(port.getValue().get(0).get("HostPort")));
                        }
                    }
                }
                if (containerInspectResponse.getHostConfig().getCpuPeriod() != 0) {
                    container.setCpu(1.0 * containerInspectResponse.getHostConfig().getCpuQuota() /
                            containerInspectResponse.getHostConfig().getCpuPeriod());
                } else {
                    container.setCpu(0d);
                }
                container.setMemory(containerInspectResponse.getHostConfig().getMemory());

                return container;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                return null;
            }
            log.warn("host {} docker id {} inspect failed, cause: {}", host, dockerId, e);
        }

        Container container = new Container();
        container.setDockerId(dockerId);
        container.setStatus("unknown");
        return container;
    }

    public static Integer inspectContainerExposedPort(String host, String dockerId, String exposedPort) {
        String url = String.format("http://%s:2375/v1.23/containers/%s/json", host, dockerId);
        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200) {
                ContainerInspectResponse containerInspectResponse = JSON.parseObject(responseEntity.getBody(),
                        ContainerInspectResponse.class);
                if (containerInspectResponse.getNetworkSettings().getNetworks() != null) {
                    for (Map.Entry<String, List<Map<String, String>>> port :
                            containerInspectResponse.getNetworkSettings().getPorts().entrySet()) {
                        if (StringUtils.equals(exposedPort, port.getKey())) {
                            return (Integer.valueOf(port.getValue().get(0).get("HostPort")));
                        }
                    }
                }
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                return null;
            }
            log.warn("host {} docker id {} inspect port {} failed, cause: {}", host, dockerId, exposedPort, e.getMessage());
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static String createContainer(String host, ContainerConfig config) {
        String url = String.format("http://%s:2375/v1.23/containers/create", host);
        try {
            ContainerCreateRequest request = new ContainerCreateRequest(config);
            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, request, String.class);
            if (responseEntity.getStatusCodeValue() == 201) {
                Map<String, String> container = JSON.parseObject(responseEntity.getBody(), Map.class);
                if (container != null) {
                    return container.get("Id");
                }
                log.error("host {} create container failed: parse null", host);
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                ImagePullListener.putTask(host, config.getImage());
            }
            log.error("host {} create container failed", host, e);
        }
        return null;
    }

    public static Result startContainer(String host, String dockerId) {
        String url = String.format("http://%s:2375/v1.23/containers/%s/start", host, dockerId);
        try {
            ResponseEntity responseEntity = restTemplate.postForEntity(url, null, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 204) {
                log.info("host {} container {} start: no error", host, dockerId);
                return Result.NO_ERROR;
            }
            if (statusCode == 304) {
                log.info("host {} container {} start: container already done", host, dockerId);
                return Result.CONTAINER_ALREADY_DONE;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 403) {
                log.warn("host {} container {} start: forbidden", host, dockerId);
                return Result.FORBIDDEN;
            }
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                log.info("host {} container {} start: no such container", host, dockerId);
                return Result.NO_SUCH_CONTAINER;
            }
            log.error("host {} container {} start: server error", host, dockerId, e);
        }
        return Result.SERVER_ERROR;
    }

    public static Boolean deleteContainer(String host, String dockerId) {
        String url = String.format("http://%s:2375/v1.23/containers/%s", host, dockerId);
        try {
            ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, null, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 204) {
                log.info("host {} delete {} success", host, dockerId);
                return true;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                return true;
            }
            log.warn("host {} delete {} failed：{}", host, dockerId, e.getMessage());
        }
        return false;
    }

    public static Result stopContainer(String host, String dockerId) {
        String url = String.format("http://%s:2375/v1.23/containers/%s/stop", host, dockerId);
        try {
            ResponseEntity responseEntity = restTemplate.postForEntity(url, null, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 204) {
                log.info("host {} container {} stop: no error", host, dockerId);
                return Result.NO_ERROR;
            }
            if (statusCode == 304) {
                log.info("host {} container {} stop: container already done", host, dockerId);
                return Result.CONTAINER_ALREADY_DONE;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                log.info("host {} container {} stop: no such container", host, dockerId);
                return Result.NO_SUCH_CONTAINER;
            }
            log.error("host {} container {} stop: server error", host, dockerId, e);
        }
        return Result.SERVER_ERROR;
    }

    @SuppressWarnings("unchecked")
    public static String createExec(String host, String dockerId, String command) {
        String url = String.format("http://%s:2375/v1.23/containers/%s/exec", host, dockerId);
        try {
            ExecCreateRequest execCreateRequest = new ExecCreateRequest(command);
            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, execCreateRequest, String.class);
            if (responseEntity.getStatusCodeValue() == 201) {
                Map<String, String> result = JSON.parseObject(responseEntity.getBody(), Map.class);
                if (result != null) {
                    log.info("host {} container {} create exec success", host, dockerId);
                    return result.get("Id");
                }
            }
        } catch (Exception e) {
            log.error("host {} container {} create exec failed", host, dockerId, e);
        }
        return null;
    }

    public static Boolean startExec(String host, String execId) {
        String url = String.format("http://%s:2375/v1.23/exec/%s/start", host, execId);
        try {
            ExecStartRequest execStartRequest = new ExecStartRequest();
            ResponseEntity responseEntity = restTemplate.postForEntity(url, execStartRequest, String.class);
            if (responseEntity.getStatusCodeValue() == 200) {
                log.info("host {} exec {} start success", host, execId);
                return true;
            }
        } catch (Exception e) {
            log.error("host {} exec {} start failed", host, execId, e);
        }
        return false;
    }

    public static Boolean updateContainer(String host, String dockerId, Double cpu, Long memory) {
        String url = String.format("http://%s:2375/v1.23/containers/%s/update", host, dockerId);
        try {
            ContainerUpdateRequest containerUpdateRequest = new ContainerUpdateRequest(cpu, memory);
            ResponseEntity responseEntity = restTemplate.postForEntity(url, containerUpdateRequest, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 200) {
                log.info("host {} container {} update: no error", host, dockerId);
                return true;
            }
        } catch (Exception e) {
            log.info("host {} container {} update failed", host, dockerId, e);
        }
        return false;
    }

    public static Boolean commitImage(String host, String dockerId, String imageName) {
        String url = String.format("http://%s:2375/v1.23/commit?container=%s&repo=%s", host, dockerId, imageName);
        try {
            ResponseEntity responseEntity = globalRestTemplate.postForEntity(url, null, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 201) {
                log.info("host {} container {} commit image {} success", host, dockerId, imageName);
                return true;
            }
        } catch (Exception e) {
            log.info("host {} container {} commit image {} failed", host, dockerId, imageName, e);
        }
        return false;
    }

    public static Boolean pushImage(String host, String imageName) {
        String url = String.format("http://%s:2375/v1.23/images/%s/push", host, imageName);
        try {
            // 上传镜像操作需要http header携带认证信息
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add("X-Registry-Auth", "{\"username\": \"cheer\"}");
            HttpEntity requestEntity = new HttpEntity(httpHeaders);
            ResponseEntity<String> responseEntity = globalRestTemplate.postForEntity(url, requestEntity, String.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 200 && !responseEntity.getBody().contains("{\"errorDetail\"")) {
                log.info("host {} push image {} success", host, imageName);
                return true;
            }
            log.error("host {} push image {} failed: {}", host, imageName, responseEntity.getBody());
        } catch (Exception e) {
            log.error("host {} push image {} failed", host, imageName, e);
        }
        return false;
    }

    public static Result pullImage(String host, String imageName) {
        try {
            String url = String.format("http://%s:2375/v1.23/images/create?fromImage=%s", host, imageName);
            ResponseEntity responseEntity = globalRestTemplate.postForEntity(url, null, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 200) {
                log.info("host {} pull image {} success", host, imageName);
                return Result.NO_ERROR;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                log.error("host {} pull image {} failed: no such image", host, imageName);
                return Result.NO_SUCH_IMAGE;
            } else {
                log.error("host {} pull image {} failed: server error", host, imageName, e);
            }
        }
        return Result.SERVER_ERROR;
    }

    public static Boolean deleteImage(String host, String imageName) {
        String url = String.format("http://%s:2375/v1.23/images/%s", host, imageName);
        try {
            ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, null, Void.class);
            int statusCode = responseEntity.getStatusCodeValue();
            if (statusCode == 200) {
                log.info("host {} image {} delete: no error", host, imageName);
                return true;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                log.info("host {} image {} delete: no such image", host, imageName);
                return true;
            }
            log.error("host {} image {} delete: server error", host, imageName, e);
        }
        return false;
    }

    public static Boolean inspectImage(String host, String imageName) {
        String url = String.format("http://%s:2375/v1.23/images/%s/json", host, imageName);
        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200) {
                return true;
            }
        } catch (Exception e) {
            if (e instanceof HttpStatusCodeException && ((HttpStatusCodeException) e).getRawStatusCode() == 404) {
                return false;
            }
            log.error("host {} image {} inspect: server error", host, imageName, e);
        }
        return false;
    }
}